package com.pxx.pj.common.aspect;

import com.pxx.pj.common.annotation.ClearCache;
import com.pxx.pj.common.annotation.RequiredCache;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Aspect
@Component
public class PxxModuleCacheAspect {//Cache切面
    /**
     * 这里希望每个业务模块都有自己对应的cache
     * 1)外层map的key对应着具体模块的cache
     * 2)内层模块key为存储数据时使用的key*/
    private Map<String,Map<String,Object>> cacheMap=new ConcurrentHashMap<>();

    //@annotation 为注解方式的切入点表达式(由此注解描述的方法为切入点方法)
    //@Pointcut("bean(sysDeptServiceImpl)")
    @Pointcut("@annotation(com.pxx.pj.common.annotation.RequiredCache)")//细粒度切入点表达式
    private void doCache(){}

    @Pointcut("@annotation(com.pxx.pj.common.annotation.ClearCache)")//细粒度切入点表达式
    private void doClearCache(){}

    @AfterReturning("doClearCache()")
    public void doAfterReturning(JoinPoint joinPoint)throws  Exception{//method ok(success)
        //Map<String,Object> cache=cacheMap.get("deptCache");
        String cacheName=getCacheName(ClearCache.class,joinPoint);
        Map<String,Object> cache=cacheMap.get(cacheName);
        cache.clear();
    }
    private Method getTargetMethod(Class<? extends Annotation> annoClass, JoinPoint joinPoint)throws Exception{
        Class<?> targetClass=joinPoint.getTarget().getClass();
        MethodSignature ms=(MethodSignature) joinPoint.getSignature();
        Method targetMethod= targetClass.getMethod(ms.getName(), ms.getParameterTypes());
        return targetMethod;
    }
    private String getCacheName(Class<? extends Annotation> annoClass, JoinPoint joinPoint)throws Exception{
        //1.获取目标方法
        Method targetMethod=getTargetMethod(annoClass, joinPoint);
        //1.基于注解类型获取目标方法上的注解对象
        Annotation annotation=targetMethod.getAnnotation(annoClass);
        String cacheName=null;
        //2.基于注解对象类型获取注解上的名字
        if(annotation instanceof RequiredCache){
            RequiredCache requiredCache= (RequiredCache)targetMethod.getAnnotation(annoClass);
            cacheName=requiredCache.name();
        }else if(annotation instanceof ClearCache){
            ClearCache clearCache= (ClearCache) targetMethod.getAnnotation(annoClass);
            cacheName=clearCache.name();
        }
        return cacheName;
    }
    private String getCacheKey(Class<? extends Annotation> annoClass, JoinPoint joinPoint)throws Exception{
        //1.获取目标方法
        Method targetMethod=getTargetMethod(annoClass, joinPoint);
        //2.基于注解类型获取目标方法上的注解对象
        Annotation annotation=targetMethod.getAnnotation(annoClass);
        String key=null;
        //3.基于注解对象类型获取注解上的名字
        if(annotation instanceof RequiredCache){
            RequiredCache requiredCache= (RequiredCache)targetMethod.getAnnotation(annoClass);
            key=requiredCache.key();
        }
        return key;
    }
    @Around("doCache()") //@Around中方法的参数连接点的类型只能写ProceedingJoinPoint
    public Object doCacheAround(ProceedingJoinPoint joinPoint)throws  Throwable{
        System.out.println("Get Data from cache");
        Map<String, Object> cache=null;
        synchronized (PxxModuleCacheAspect.class) {
            String cacheName = getCacheName(RequiredCache.class, joinPoint);
            cache = cacheMap.get(cacheName);
            if (cache == null) {
                cache = new ConcurrentHashMap<>();//为对应key创建cache对象
                cacheMap.put(cacheName, cache);
            }
        }
        String key=getCacheKey(RequiredCache.class,joinPoint);
        Object result=cache.get(key);//暂时先自己指定一个key的名字
        if(result!=null)return result;
        result=joinPoint.proceed();
        System.out.println("Put data to cache");
        cache.put(key, result);
        return result;
    }
}
